/*
    control模块：主要负责oj_server业务的核心逻辑控制，根据上层请求，获取数据、加工数据、交付数据以及负载均衡选择，向编译网络服务发送编译请求
    1、根据上层请求，调用model获取题目列表数据、题目详细信息
    2、将model模块获取的数据交给view模块渲染成网页，将网页交付给上层
    3、对model模块获取的数据进行加工，构建编译请求，负载均衡选择，发送给编译网络服务，并将响应结果交付给上层
*/
#pragma once
#include <vector>
#include <mutex>
#include <jsoncpp/json/json.h>
#include <cassert>
#include <fstream>
//#include "model.hpp" //文件版本
#include "model_mysql.hpp"
#include "view.hpp"
#include "../util/log.hpp"
#include "../util/util.hpp"
#include "../util/httplib.h"

namespace ns_control
{
    using namespace ns_model;
    using namespace ns_view;
    using namespace ns_util;
    using namespace httplib;

    class Machine // 服务主机
    {
    public:
        Machine()
        {
        }
        Machine(std::string ip, int port) : _ip(ip), _port(port), _factor(0), _online(true)
        {
            _factor_mtx = new std::mutex();
            _online_mtx = new std::mutex();
        }
        ~Machine()
        {
        }

        Machine &operator==(const Machine &m)
        {
            m._factor_mtx->lock();
            _ip = m._ip;
            _port = m._port;
            _online = m._online;
            _factor_mtx = new std::mutex();
            m._factor_mtx->unlock();
            return *this;
        }

        std::string getIp()
        {
            return _ip;
        }

        int getPort()
        {
            return _port;
        }

        // 增加负载因子
        void inFactor()
        {
            _factor_mtx->lock();
            _factor++;
            _factor_mtx->unlock();
        }

        // 降低负载因子
        void deFactor()
        {
            _factor_mtx->lock();
            _factor--;
            _factor_mtx->unlock();
        }

        // 获取负载因子
        int factor()
        {
            int ret = 0;
            _factor_mtx->lock();
            ret = _factor;
            _factor_mtx->unlock();
            return ret;
        }

        /*
            上下线操作，以及判断上线操作必须持有锁，因为判断的时候可能有线程正在改动
        */
        // 上线
        void onlineOn()
        {
            _online_mtx->lock();
            _online = true;
            _online_mtx->unlock();
        }

        // 下线
        void onlineOff()
        {
            _online_mtx->lock();
            _online = false;
            _factor = 0; // 下线的同时应将负载因子清0，避免影响下次上线
            _online_mtx->unlock();
        }

        // 判断是否上线
        bool isOnline()
        {
            bool ret = true;
            _online_mtx->lock();
            if (_online == false)
                ret = false;
            _online_mtx->unlock();
            return ret;
        }

        void showInfo()
        {
            _online_mtx->lock();
            std::cout << "port:" << _port << std::endl;
            std::cout << "online:" << (_online == false ? 0 : 1) << std::endl;
            std::cout << "addr:" << this << std::endl;
            _online_mtx->unlock();
        }

    private:
        std::string _ip; // 主机ip
        int _port;       // 服务端口
        int _factor;     // 负载因子
        bool _online;    // 当前主机是否宕机
        std::mutex *_factor_mtx;
        /*
            二元锁指针，用于保证factor线程安全，因为一台主机可能有多个服务，
           其中mutex禁止拷贝，而Machine随时可能发生拷贝，所以定义为指针
        */
        std::mutex *_online_mtx; // 保护_online
    };

    const std::string machine_conf_file = "./conf/server_machines.conf";

    class LoadBalancer // 负载均衡器
    {
    private:
        bool loadMachines() // 加载并上线所有主机
        {
            std::ifstream in(machine_conf_file);
            if (!in.is_open())
            {
                LOG(FATAL) << "主机配置文件打开失败\n";
                return false;
            }

            std::string line;
            while (getline(in, line))
            {
                std::vector<std::string> tokens;
                StrUtil::splitStr(line, tokens, " ");
                _machines.emplace_back(tokens[0], stoi(tokens[1]));
            }
            return true;
        }

    public:
        LoadBalancer()
        {
            assert(loadMachines());
            _mtx = new std::mutex();
            LOG(INFO) << "所有主机上线成功\n";
        }
        ~LoadBalancer()
        {
        }

        // 选择一台负载最小的主机
        bool choice(Machine *&machine)
        {
            // 负载均衡选择算法
            // 1、随机+hash
            // 2、轮询+hash
            _mtx->lock();
            int min = 0;
            int i = 0;
            for (i = 0; i < _machines.size(); i++) // 默认选择第一个没有下线的主机
            {
                if (_machines[i].isOnline())
                {
                    min = _machines[i].factor();
                    machine = &_machines[i];
                    break;
                }
            }
            if (i == _machines.size()) // 所有主机下线
            {
                _mtx->unlock();
                return false;
            }
            for (; i < _machines.size(); i++) // 接着后面选
            {
                int n = _machines[i].factor();
                if (_machines[i].isOnline() && (min > n))
                {
                    min = n;
                    machine = &_machines[i];
                }
            }
            _mtx->unlock();
            return true;
        }

        // 上线所有下线主机
        void recovery()
        {
            for (auto &m : _machines)
            {
                if (!m.isOnline())
                {
                    m.onlineOn();
                    LOG(INFO) << "主机：[" << m.getIp() << "]" << "[" << m.getPort() << "]上线\n";
                }
            }
        }

        void show()
        {
            for (auto &e : _machines)
            {
                e.showInfo();
            }
        }

    private:
        std::vector<Machine> _machines; // 可供服务的所有主机
        std::mutex *_mtx;
    };

    class Control // 业务逻辑控制
    {
    public:
        Control()
        {
        }
        ~Control()
        {
        }

        // 上线所有主机
        void recoveryAllMachine()
        {
            _loadbalancer.recovery();
        }

        // 渲染题目列表网页
        bool questionsListHtml(std::string &html)
        {
            // 获取题目列表
            std::vector<Item> tokens;
            if (!_model.getQuestionsList(tokens))
            {
                LOG(FATAL) << "获取所有题目列表失败\n";
                return false;
            }

            // 对获取到的题目列表按题目编号排升序
            sort(tokens.begin(), tokens.end(), [](const struct Item& it1, const struct Item& it2)
            {
                return stoi(it1.number) < stoi(it2.number);
            });

            // 渲染
            _view.expandQuestionslist(tokens, html);
            return true;
        }

        // 渲染具体题目网页
        bool questionHtml(std::string &number, std::string &html)
        {
            // 获取题目信息
            Question ques;
            if (!_model.getQuestion(number, ques))
            {
                LOG(FATAL) << "获取题目" << number << "失败\n";
                return false;
            }

            // 渲染
            _view.expandQuestion(ques, html);
            return true;
        }

        // 负载均衡式选择服务编译运行代码
        // in_json格式
        // {
        //     "code":...
        //     "input":...
        // }
        void judge(const std::string &number, const std::string &in_json, std::string &out_json)
        {
            //_loadbalancer.show();
            // 1、in_json反序列化
            Json::Value in;
            Json::Reader rd;
            rd.parse(in_json, in);

            // 2、构建向编译服务请求的正文（json串）
            Json::Value compile_val;
            Question ques;
            Json::StyledWriter wr;
            if (!_model.getQuestion(number, ques))
                LOG(ERROR) << "题目获取失败\n";
            compile_val["code"] = in["code"].asString() + "\n" + ques.tail; // 用户提交源代码和测试代码结合
            compile_val["input"] = in["input"].asString();                  // 用户自测输入（扩展）
            compile_val["cpulimit"] = ques.item.cpulimit;
            compile_val["memlimit"] = ques.item.memlimit;
            std::string compile_json = wr.write(compile_val);

            int cnt = 3; // cnt:允许选择主机失败的次数
            while (cnt)
            {
                // 3、选择服务主机
                Machine *mp = nullptr;
                if (!_loadbalancer.choice(mp))
                {
                    LOG(FATAL) << "所有主机宕机\n";
                    break;
                }
                mp->inFactor(); // 负载因子增加
                LOG(INFO) << "主机被选中：[" << mp->getIp() << "]" << "[" << mp->getPort() << "]" << " 负载因子: " << mp->factor() << "\n";

                // 4、定义客户端服务，给主机发送请求获得响应
                Client clt(mp->getIp(), mp->getPort());
                if (auto res = clt.Post("/compiler_server", compile_json, "application/json;charset=utf-8"))
                {
                    out_json = res->body;
                    if (res->status == 200)
                        LOG(INFO) << "编译运行服务请求成功\n";
                    else
                        LOG(INFO) << "编译运行服务请求发生未知错误\n";
                    mp->deFactor(); // 减少负载因子
                    break;
                }
                else
                {
                    LOG(ERROR) << "编译服务请求失败，主机：[" << mp->getIp() << "]" << "[" << mp->getPort() << "]"
                               << "可能已离线，负载因子: " << mp->factor() << "，尝试连接：" << cnt << std::endl;
                    mp->onlineOff(); // 将当前主机下线
                    cnt--;
                }
            }
            if(cnt == 0)
            {
                LOG(ERROR) << "编译服务请求失败，尝试连接：" << cnt << std::endl;
            }
        }

    private:
        Model _model;               // 数据的管理
        View _view;                 // 数据的渲染
        LoadBalancer _loadbalancer; // 服务的选择
    };
}
